package com.shamoa.springbootredis.util;

import cn.hutool.core.util.ObjectUtil;
import com.shamoa.springbootredis.factory.RedisScriptFactory;
import io.netty.util.HashedWheelTimer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author zy
 * @version 1.0
 * @date 2024/3/24 17:15
 * @description redis工具类
 **/
@Component
public class RedisUtil {

    @Resource
    private RedisTemplate redisTemplate;

    // 过期时间默认61秒
    private static final int DEFAULT_SINGLE_EXPIRE_TIME = 61;
    private static final int SEQ_START_ID = 100;
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
    private static final long THIRTY_SECONDS = 1000 * 30;

    /**
     * 锁在给定时间范围内获取空闲则获取成功
     * 看情况是否需要自旋获取锁
     *
     * @param key
     * @param value
     * @param time     尝试获取锁的时间
     * @param timeUnit 时间单位
     * @return
     * @throws InterruptedException
     */
    public Boolean tryLock(String key, String value, long time, TimeUnit timeUnit) throws InterruptedException {
        long begin = System.nanoTime();
        if (Objects.isNull(timeUnit)) {
            timeUnit = TimeUnit.SECONDS;
        }
        do {
            Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, value);
            if (ObjectUtil.isNotNull(absent) && absent) {
                // 设置锁的过期时间，避免未释放
                redisTemplate.expire(key, DEFAULT_SINGLE_EXPIRE_TIME, TimeUnit.SECONDS);
                return true;
            }
            if (time == 0) {
                break;
            }
            Thread.sleep(100);
        } while ((System.nanoTime() - begin) < timeUnit.toNanos(time));
        // 超时未获取锁
        return false;
    }

    /**
     * 释放锁
     */
    public Boolean unLock(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 获取当天唯一id，key过期时间为一小时
     */
    public String dailyNextId(String key) {
        return this.value(key);
    }

    private String value(String key) {
        String today = LocalDate.now().format(FORMATTER);

        DefaultRedisScript<String> redisScript = RedisScriptFactory.getInstance().buildRedisScript("luaSerialIdScript.lua", String.class);
        Object result = redisTemplate.execute(redisScript, Collections.singletonList(key),
                SEQ_START_ID
        );
        return today + result;
    }

    /**
     * 获取当前key的下一个唯一id
     */
    public String nextValue(String key) {
        return value(key);
    }

    /**
     * 上锁，可重入锁，默认30秒过期，有看门狗自动续
     */
    public boolean tryLock(String key) {
        return this.tryLock(key, THIRTY_SECONDS);
    }

    /**
     * 上锁，可重入锁，有看门狗自动续
     */
    public boolean tryLock(String key, Long seconds) {
        String threadId = "ThreadId:" + Thread.currentThread().getId();
        System.out.println("threadId = " + threadId);

        DefaultRedisScript<Long> redisScript = RedisScriptFactory.getInstance().buildRedisScript("luaRedissionKey.lua", Long.class);

        Object result = redisTemplate.execute(redisScript, Collections.singletonList(key),
                threadId, seconds
        );
        if (ObjectUtil.isNull(result)) {
            HashedWheelTimer timer = new HashedWheelTimer();
            reExpire(key, threadId, seconds, timer);
            return true;
        }
        return false;
    }

    /**
     * 按传入的时间的三分之一进行续锁
     */
    private void reExpire(String key, String value, long lockTime, HashedWheelTimer timer) {
        timer.newTimeout(timeout -> {
            DefaultRedisScript<Boolean> redisScript = RedisScriptFactory.getInstance().buildRedisScript("luaWatchDog.lua", Boolean.class);
            Boolean reExpireResult = (Boolean) redisTemplate.execute(redisScript, Collections.singletonList(key),
                    value, THIRTY_SECONDS
            );
            if (reExpireResult) {
                System.out.println("续锁 : " + key);
                this.reExpire(key, value, lockTime, timer);
            } else {
                System.out.println("停止这个key : " + key + " 的看门狗");
                return;
            }
        }, lockTime / 3 / 1000, TimeUnit.SECONDS);
    }


}
